using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace ServiceStack.OrmLite;

public static class UntypedApiExtensions
{
    static readonly ConcurrentDictionary<Type, Type> untypedApiMap =
        new ConcurrentDictionary<Type, Type>();

    public static IUntypedApi CreateTypedApi(this IDbConnection db, Type forType)
    {
        var genericType = untypedApiMap.GetOrAdd(forType, key => typeof(UntypedApi<>).GetCachedGenericType(key));
        var unTypedApi = genericType.CreateInstance<IUntypedApi>();
        unTypedApi.Db = db;
        return unTypedApi;
    }

    public static IUntypedApi CreateTypedApi(this IDbCommand dbCmd, Type forType)
    {
        var genericType = untypedApiMap.GetOrAdd(forType, key => typeof(UntypedApi<>).GetCachedGenericType(key));
        var unTypedApi = genericType.CreateInstance<IUntypedApi>();
        unTypedApi.DbCmd = dbCmd;
        return unTypedApi;
    }

    public static IUntypedApi CreateTypedApi(this Type forType)
    {
        var genericType = untypedApiMap.GetOrAdd(forType, key => typeof(UntypedApi<>).GetCachedGenericType(key));
        var unTypedApi = genericType.CreateInstance<IUntypedApi>();
        return unTypedApi;
    }
}

public class UntypedApi<T> : IUntypedApi
{
    public IDbConnection Db { get; set; }
    public IDbCommand DbCmd { get; set; }

    public TReturn Exec<TReturn>(Func<IDbCommand, TReturn> filter)
    {
        return DbCmd != null ? filter(DbCmd) : Db.Exec(filter);
    }

    public Task<TReturn> Exec<TReturn>(Func<IDbCommand, Task<TReturn>> filter)
    {
        return DbCmd != null ? filter(DbCmd) : Db.Exec(filter);
    }

    public void Exec(Action<IDbCommand> filter)
    {
        if (DbCmd != null)
            filter(DbCmd);
        else
            Db.Exec(filter);
    }

    public int SaveAll(IEnumerable objs)
    {
        return Exec(dbCmd => dbCmd.SaveAll((IEnumerable<T>)objs));
    }

    public bool Save(object obj)
    {
        return Exec(dbCmd => dbCmd.Save((T)obj));
    }

    public Task<int> SaveAllAsync(IEnumerable objs, CancellationToken token)
    {
        return Exec(dbCmd => dbCmd.SaveAllAsync((IEnumerable<T>)objs, token));
    }

    public Task<bool> SaveAsync(object obj, CancellationToken token)
    {
        return Exec(dbCmd => dbCmd.SaveAsync((T)obj, token));
    }

    public void InsertAll(IEnumerable objs)
    {
        Exec(dbCmd => dbCmd.InsertAll((IEnumerable<T>)objs, commandFilter:null));
    }

    public void InsertAll(IEnumerable objs, Action<IDbCommand> commandFilter)
    {
        Exec(dbCmd => dbCmd.InsertAll((IEnumerable<T>)objs, commandFilter:commandFilter));
    }

    public long Insert(object obj, bool selectIdentity = false)
    {
        return Exec(dbCmd => dbCmd.Insert((T)obj, commandFilter: null, selectIdentity: selectIdentity));
    }

    public long Insert(object obj, Action<IDbCommand> commandFilter, bool selectIdentity = false)
    {
        return Exec(dbCmd => dbCmd.Insert((T)obj, commandFilter: commandFilter, selectIdentity: selectIdentity));
    }

    public int UpdateAll(IEnumerable objs)
    {
        return Exec(dbCmd => dbCmd.UpdateAll((IEnumerable<T>)objs));
    }

    public int UpdateAll(IEnumerable objs, Action<IDbCommand> commandFilter)
    {
        return Exec(dbCmd => dbCmd.UpdateAll((IEnumerable<T>)objs, commandFilter: commandFilter));
    }

    public int Update(object obj)
    {
        return Exec(dbCmd => dbCmd.Update((T)obj));
    }

    public Task<int> UpdateAsync(object obj, CancellationToken token)
    {
        return Exec(dbCmd => dbCmd.UpdateAsync((T)obj, token));
    }

    public int Update(object obj, Action<IDbCommand> commandFilter)
    {
        return Exec(dbCmd => dbCmd.Update((T)obj, commandFilter: commandFilter));
    }

    public int DeleteAll()
    {
        return Exec(dbCmd => dbCmd.DeleteAll<T>());
    }

    public int Delete(object obj, object anonType)
    {
        return Exec(dbCmd => dbCmd.Delete<T>(anonType));
    }

    public int DeleteNonDefaults(object obj, object filter)
    {
        return Exec(dbCmd => dbCmd.DeleteNonDefaults((T)filter));
    }

    public int DeleteById(object id)
    {
        return Exec(dbCmd => dbCmd.DeleteById<T>(id));
    }

    public int DeleteByIds(IEnumerable idValues)
    {
        return Exec(dbCmd => dbCmd.DeleteByIds<T>(idValues));
    }

    public IEnumerable Cast(IEnumerable results)
    {
        return (from object result in results select (T)result).ToList();
    }
}